﻿//Copyright (c) 2009, Offbeat Guides
//All rights reserved.
//
//Redistribution and use in source and binary forms, with or without modification, are 
//permitted provided that the following conditions are met:
//
//* Redistributions of source code must retain the above copyright notice, this list of 
//conditions and the following disclaimer.
//
//* Redistributions in binary form must reproduce the above copyright notice, this list 
//of conditions and the following disclaimer in the documentation and/or other materials 
//provided with the distribution.
//
//* Neither the name of Offbeat Guides nor the names of its contributors may be used to 
//endorse or promote products derived from this software without specific prior written 
//permission.
//
//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
//EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
//OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
//SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
//SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
//OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
//HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 
//TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
//EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.ComponentModel;
using Amazon.Services.S3.Samples.Properties;
using Amazon.Services.S3.Forms;

namespace Amazon.Services.S3.Controls
{
    public class S3Explorer : System.Windows.Forms.TreeView
    {
        #region Variables
        private Amazon.Services.AmazonAccount _account;

        private System.Windows.Forms.ContextMenuStrip mnuAccount;
        private System.ComponentModel.IContainer components;
        private System.Windows.Forms.ContextMenuStrip mnuDrive;
        private System.Windows.Forms.ContextMenuStrip mnuDirectory;
        private System.Windows.Forms.ToolStripMenuItem mnuAddDrive;
        private System.Windows.Forms.ToolStripSeparator toolStripSeparator1;
        private ToolStripMenuItem mnuAddDirectory;
        private ToolStripSeparator toolStripSeparator2;
        private ToolStripMenuItem mnuDeleteDrive;
        private ToolStripMenuItem mnuRefreshDrive;
        private ToolStripMenuItem mnuDeleteDirectory;
        private ToolStripMenuItem mnuRefreshDirectory;
        private ToolStripMenuItem mnuAddSubDirectory;
        private ToolStripSeparator toolStripSeparator3;
        private System.Windows.Forms.ToolStripMenuItem mnuRefreshAccount;
        #endregion

        #region Properties
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public AmazonAccount Account
        {
            get { return this._account; }
            set
            {
                this._account = value;
                // this.RefreshDrives();
            }
        }

        private Boolean IsUpdating { get; set; }
        #endregion

        #region Constructors and Finalizers
        public S3Explorer()
        {
            this.Account = new AmazonAccount(Settings.Default.AccessID, Settings.Default.SecretAccessID);

            this.ImageList = new ImageList();
            this.ImageList.Images.Add(Resources.document_16);
            this.ImageList.Images.Add(Resources.folder_16);
            this.ImageList.Images.Add(Resources.stop_16);
            this.ImageList.Images.Add(Resources.globe_16);

            this.InitializeComponent();
        }
        #endregion

        #region Methods
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.mnuAccount = new System.Windows.Forms.ContextMenuStrip(this.components);
            this.mnuAddDrive = new System.Windows.Forms.ToolStripMenuItem();
            this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
            this.mnuRefreshAccount = new System.Windows.Forms.ToolStripMenuItem();
            this.mnuDrive = new System.Windows.Forms.ContextMenuStrip(this.components);
            this.mnuAddDirectory = new System.Windows.Forms.ToolStripMenuItem();
            this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
            this.mnuDeleteDrive = new System.Windows.Forms.ToolStripMenuItem();
            this.mnuRefreshDrive = new System.Windows.Forms.ToolStripMenuItem();
            this.mnuDirectory = new System.Windows.Forms.ContextMenuStrip(this.components);
            this.mnuDeleteDirectory = new System.Windows.Forms.ToolStripMenuItem();
            this.mnuRefreshDirectory = new System.Windows.Forms.ToolStripMenuItem();
            this.mnuAddSubDirectory = new System.Windows.Forms.ToolStripMenuItem();
            this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator();
            this.mnuAccount.SuspendLayout();
            this.mnuDrive.SuspendLayout();
            this.mnuDirectory.SuspendLayout();
            this.SuspendLayout();
            // 
            // mnuAccount
            // 
            this.mnuAccount.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
            this.mnuAddDrive,
            this.toolStripSeparator1,
            this.mnuRefreshAccount});
            this.mnuAccount.Name = "mnuAccount";
            this.mnuAccount.Size = new System.Drawing.Size(136, 54);
            // 
            // mnuAddDrive
            // 
            this.mnuAddDrive.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
            this.mnuAddDrive.Name = "mnuAddDrive";
            this.mnuAddDrive.Size = new System.Drawing.Size(135, 22);
            this.mnuAddDrive.Text = "Add Drive...";
            this.mnuAddDrive.Click += new System.EventHandler(this.mnuAddDrive_Click);
            // 
            // toolStripSeparator1
            // 
            this.toolStripSeparator1.Name = "toolStripSeparator1";
            this.toolStripSeparator1.Size = new System.Drawing.Size(132, 6);
            // 
            // mnuRefreshAccount
            // 
            this.mnuRefreshAccount.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
            this.mnuRefreshAccount.Name = "mnuRefreshAccount";
            this.mnuRefreshAccount.Size = new System.Drawing.Size(135, 22);
            this.mnuRefreshAccount.Text = "Refresh";
            this.mnuRefreshAccount.Click += new System.EventHandler(this.mnuRefreshAccount_Click);
            // 
            // mnuDrive
            // 
            this.mnuDrive.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
            this.mnuAddDirectory,
            this.toolStripSeparator2,
            this.mnuDeleteDrive,
            this.mnuRefreshDrive});
            this.mnuDrive.Name = "mnuDrive";
            this.mnuDrive.Size = new System.Drawing.Size(157, 76);
            // 
            // mnuAddDirectory
            // 
            this.mnuAddDirectory.Name = "mnuAddDirectory";
            this.mnuAddDirectory.Size = new System.Drawing.Size(156, 22);
            this.mnuAddDirectory.Text = "Add Directory...";
            this.mnuAddDirectory.Click += new System.EventHandler(this.mnuAddDirectory_Click);
            // 
            // toolStripSeparator2
            // 
            this.toolStripSeparator2.Name = "toolStripSeparator2";
            this.toolStripSeparator2.Size = new System.Drawing.Size(153, 6);
            // 
            // mnuDeleteDrive
            // 
            this.mnuDeleteDrive.Name = "mnuDeleteDrive";
            this.mnuDeleteDrive.Size = new System.Drawing.Size(156, 22);
            this.mnuDeleteDrive.Text = "Delete...";
            this.mnuDeleteDrive.Click += new System.EventHandler(this.mnuDeleteDrive_Click);
            // 
            // mnuRefreshDrive
            // 
            this.mnuRefreshDrive.Name = "mnuRefreshDrive";
            this.mnuRefreshDrive.Size = new System.Drawing.Size(156, 22);
            this.mnuRefreshDrive.Text = "Refresh";
            this.mnuRefreshDrive.Click += new System.EventHandler(this.mnuRefreshDrive_Click);
            // 
            // mnuDirectory
            // 
            this.mnuDirectory.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
            this.mnuAddSubDirectory,
            this.toolStripSeparator3,
            this.mnuDeleteDirectory,
            this.mnuRefreshDirectory});
            this.mnuDirectory.Name = "mnuDirectory";
            this.mnuDirectory.Size = new System.Drawing.Size(157, 76);
            // 
            // mnuDeleteDirectory
            // 
            this.mnuDeleteDirectory.Name = "mnuDeleteDirectory";
            this.mnuDeleteDirectory.Size = new System.Drawing.Size(156, 22);
            this.mnuDeleteDirectory.Text = "Delete";
            this.mnuDeleteDirectory.Click += new System.EventHandler(this.mnuDeleteDirectory_Click);
            // 
            // mnuRefreshDirectory
            // 
            this.mnuRefreshDirectory.Name = "mnuRefreshDirectory";
            this.mnuRefreshDirectory.Size = new System.Drawing.Size(156, 22);
            this.mnuRefreshDirectory.Text = "Refresh";
            this.mnuRefreshDirectory.Click += new System.EventHandler(this.mnuRefreshDirectory_Click);
            // 
            // mnuAddSubDirectory
            // 
            this.mnuAddSubDirectory.Name = "mnuAddSubDirectory";
            this.mnuAddSubDirectory.Size = new System.Drawing.Size(156, 22);
            this.mnuAddSubDirectory.Text = "Add Directory...";
            this.mnuAddSubDirectory.Click += new EventHandler(mnuAddSubDirectory_Click);
            // 
            // toolStripSeparator3
            // 
            this.toolStripSeparator3.Name = "toolStripSeparator3";
            this.toolStripSeparator3.Size = new System.Drawing.Size(153, 6);
            // 
            // S3Explorer
            // 
            this.LineColor = System.Drawing.Color.Black;
            this.mnuAccount.ResumeLayout(false);
            this.mnuDrive.ResumeLayout(false);
            this.mnuDirectory.ResumeLayout(false);
            this.ResumeLayout(false);

        }

        private void ClearChildNodes(TreeNode node)
        {
            if (node == null)
                this.Nodes.Clear();
            else
                node.Nodes.Clear();
        }

        private void AddNode(TreeNode parent, TreeNode node)
        {
            if (parent == null)
                this.Nodes.Add(node);
            else
                parent.Nodes.Add(node);
        }

        private void UpdateNode(Object value)
        {
            this.UpdateNode(value as TreeNode);
        }

        private void UpdateNode(TreeNode parent)
        {
            if (parent == null)
            {
                TreeNode node = new TreeNode(String.IsNullOrEmpty(this.Account.Name) ? "Amazon" : this.Account.Name);
                node.ImageIndex = 3;
                node.SelectedImageIndex = 3;
                node.StateImageIndex = 3;
                node.Tag = this.Account;
                node.ContextMenuStrip = this.mnuAccount;
                this.Invoke(new AddNodeHandler(this.AddNode), null, node);

                ThreadPool.QueueUserWorkItem(new WaitCallback(this.UpdateNode), node);
            }
            else if (parent.Tag is AmazonAccount)
                this.UpdateAccountNode(parent, parent.Tag as AmazonAccount);
            else if (parent.Tag is DriveInfo)
                this.UpdateDriveNode(parent, parent.Tag as DriveInfo);
            else if (parent.Tag is DirectoryInfo)
                this.UpdateDirectoryNode(parent, parent.Tag as DirectoryInfo);
        }

        public void UpdateAccountNode(TreeNode parent, AmazonAccount account)
        {
            DriveInfo[] drives = DriveInfo.GetDrives(account);
            for (int i = 0; i < drives.Length; i++)
            {
                DriveInfo drive = drives[i];

                TreeNode node = new TreeNode(drive.Name);
                node.ImageIndex = 2;
                node.SelectedImageIndex = 2;
                node.StateImageIndex = 2;
                node.Tag = drive;
                node.ContextMenuStrip = this.mnuDrive;
                this.Invoke(new AddNodeHandler(this.AddNode), parent, node);

                ThreadPool.QueueUserWorkItem(new WaitCallback(this.UpdateNode), node);
            }
        }

        public void UpdateDriveNode(TreeNode parent, DriveInfo drive)
        {
            DirectoryInfo[] directories = drive.RootDirectory.GetDirectories();
            for (int i = 0; i < directories.Length; i++)
            {
                DirectoryInfo directory = directories[i];

                TreeNode node = new TreeNode(directory.Name);
                node.ImageIndex = 1;
                node.SelectedImageIndex = 1;
                node.StateImageIndex = 1;
                node.Tag = directory;
                node.ContextMenuStrip = this.mnuDirectory;
                this.Invoke(new AddNodeHandler(this.AddNode), parent, node);

                ThreadPool.QueueUserWorkItem(new WaitCallback(this.UpdateNode), node);
            }
        }

        public void UpdateDirectoryNode(TreeNode parent, DirectoryInfo parentDirectory)
        {
            DirectoryInfo[] directories = parentDirectory.GetDirectories();
            for (int i = 0; i < directories.Length; i++)
            {
                DirectoryInfo directory = directories[i];

                TreeNode node = new TreeNode(directory.Name);
                node.ImageIndex = 1;
                node.SelectedImageIndex = 1;
                node.StateImageIndex = 1;
                node.Tag = directory;
                node.ContextMenuStrip = this.mnuDirectory;
                this.Invoke(new AddNodeHandler(this.AddNode), parent, node);

                ThreadPool.QueueUserWorkItem(new WaitCallback(this.UpdateNode), node);
            }
        }
        #endregion

        #region Overriden Methods
        protected override void OnHandleCreated(EventArgs e)
        {
            base.OnHandleCreated(e);

            if (!this.DesignMode)
                ThreadPool.QueueUserWorkItem(new WaitCallback(this.UpdateNode), null);
        }

        protected override void OnMouseClick(MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                TreeNode node = this.GetNodeAt(e.Location);
                this.SelectedNode = node;
            }

            base.OnMouseClick(e);
        }

        protected override void OnAfterSelect(TreeViewEventArgs e)
        {
            base.OnAfterSelect(e);

            if (e.Node.Tag is AmazonAccount)
                this.OnAccountNodeSelected(new AccountEventArgs(e.Node, e.Node.Tag as AmazonAccount));
            else if (e.Node.Tag is DriveInfo)
                this.OnDriveNodeSelected(new DriveEventArgs(e.Node, e.Node.Tag as DriveInfo));
            else if (e.Node.Tag is DirectoryInfo)
                this.OnDirectoryNodeSelected(new DirectoryEventArgs(e.Node, e.Node.Tag as DirectoryInfo));
        }

        protected void OnAccountNodeSelected(AccountEventArgs e)
        {
            if (this.AccountNodeSelected != null)
                this.AccountNodeSelected(this, e);
        }

        protected void OnDriveNodeSelected(DriveEventArgs e)
        {
            if (this.DriveNodeSelected != null)
                this.DriveNodeSelected(this, e);
        }

        protected void OnDirectoryNodeSelected(DirectoryEventArgs e)
        {
            if (this.DirectoryNodeSelected != null)
                this.DirectoryNodeSelected(this, e);
        }
        #endregion

        #region Event Handlers
        void mnuAddDrive_Click(object sender, EventArgs e)
        {
            if (!(this.SelectedNode.Tag is AmazonAccount))
                return;

            String name;
            if (UserInputBox.Show("Drive name:", "Add a new drive", out name) == DialogResult.OK)
            {
                DriveInfo drive = DriveInfo.Create(this.SelectedNode.Tag as AmazonAccount, name);
                
                this.SelectedNode.Nodes.Clear();
                this.UpdateNode(this.SelectedNode);
            }
        }

        void mnuRefreshAccount_Click(object sender, EventArgs e)
        {
            if (!(this.SelectedNode.Tag is AmazonAccount))
                return;

            this.SelectedNode.Nodes.Clear();
            this.UpdateNode(this.SelectedNode);
        }

        void mnuRefreshDrive_Click(object sender, EventArgs e)
        {
            if (!(this.SelectedNode.Tag is DriveInfo))
                return;

            this.SelectedNode.Nodes.Clear();
            this.UpdateNode(this.SelectedNode);
        }

        void mnuDeleteDrive_Click(object sender, EventArgs e)
        {
            if (!(this.SelectedNode.Tag is DriveInfo))
                return;

            if (MessageBox.Show("Are you sure you want to delete this drive?", "Delete drive", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes)
            {
                DriveInfo drive = this.SelectedNode.Tag as DriveInfo;
                drive.RootDirectory.Delete(true);
                drive.Delete();

                this.SelectedNode.Parent.Nodes.Clear();
                this.UpdateNode(this.SelectedNode);
            }
        }

        void mnuAddDirectory_Click(object sender, EventArgs e)
        {
            if (!(this.SelectedNode.Tag is DriveInfo))
                return;

            String name;
            if (UserInputBox.Show("Directory Name:", "Add Directory", out name) == DialogResult.OK)
            {
                DriveInfo drive = this.SelectedNode.Tag as DriveInfo;
                drive.RootDirectory.CreateSubdirectory(name);

                this.SelectedNode.Nodes.Clear();
                this.UpdateNode(this.SelectedNode);
            }
        }

        void mnuRefreshDirectory_Click(object sender, EventArgs e)
        {
            if (!(this.SelectedNode.Tag is DirectoryInfo))
                return;

            this.SelectedNode.Nodes.Clear();
            this.UpdateNode(this.SelectedNode);
        }

        void mnuDeleteDirectory_Click(object sender, EventArgs e)
        {
            if (!(this.SelectedNode.Tag is DirectoryInfo))
                return;

            if (MessageBox.Show("Are you sure you want to delete this directory?", "Delete directory", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes)
            {
                DirectoryInfo directory = this.SelectedNode.Tag as DirectoryInfo;
                directory.Delete(true);

                this.SelectedNode.Parent.Nodes.Clear();
                this.UpdateNode(this.SelectedNode);
            }
        }

        void mnuAddSubDirectory_Click(object sender, EventArgs e)
        {
            if (!(this.SelectedNode.Tag is DirectoryInfo))
                return;

            String name;
            if (UserInputBox.Show("Directory Name:", "Add Directory", out name) == DialogResult.OK)
            {
                DirectoryInfo directory = this.SelectedNode.Tag as DirectoryInfo;
                directory.CreateSubdirectory(name);

                this.SelectedNode.Nodes.Clear();
                this.UpdateNode(this.SelectedNode);
            }
        }
        #endregion

        #region Events
        public event AccountSelectedHandler AccountNodeSelected;
        public event DriveSelectedHandler DriveNodeSelected;
        public event DirectorySelectedHandler DirectoryNodeSelected;
        #endregion

        #region Delegates
        private delegate void ClearChildNodesHandler(TreeNode node);
        private delegate void AddNodeHandler(TreeNode parent, TreeNode node);
        #endregion
    }
}
